# -*- coding: utf-8 -*-
"""
Created on Sun May 14 19:32:10 2017

@author: GangTimes
"""

import numpy
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D 
import matplotlib.font_manager as fm


#绘图设置
zh_font=fm.FontProperties(fname=r"c:\windows\fonts\simsun.ttf",size=18)#路径用小写  大写不行 有这个不能保存为eps
en_font=fm.FontProperties(family='monospace',size=18)
zf_font=fm.FontProperties(family='Times New Roman',style='italic',size=16)

fig_model=plt.figure(figsize=(7,5))
ax_model=Axes3D(fig_model,[0.,0.0,1,1])
plt.rcParams['axes.unicode_minus']=False #用来正常显示负

#全局参数
num=200


def self_plot(x,y,z,f):
    x1=[]
    x2=[]
    y1=[]
    y2=[]
    z1=[]
    z2=[]
    
    for index in range(num):
        if f[index,0]==1:
            x1.append(x[index,0])
            y1.append(y[index,0])
            z1.append(z[index,0])
        else:
            x2.append(x[index,0])
            y2.append(y[index,0])
            z2.append(z[index,0])
    
    ax_model.scatter(x1,y1,z1,c='r',marker='o')
    ax_model.scatter(x2,y2,z2,c='b',marker='^')

    
def create_data(theta):
    '''
    用于生成一个二维空间的两种类型的点
    人为制造一定的误差
    return:matrix[nx3] n为样本数量  [x y z]三列
    '''
    x=numpy.matrix(numpy.random.uniform(0,1,num)).T
    y=numpy.matrix(numpy.random.uniform(0,1,num)).T  
    z=numpy.matrix(numpy.random.uniform(0,1,num)).T  
    x_one=numpy.matrix(numpy.ones((num,1)))
    
    f=model_fun(theta,numpy.hstack((x,y,z,x_one)))
    self_plot(x,y,z,f)
    
    return numpy.hstack((x,y,z,f))            
            
def model_fun(theta,x):
    '''
    这是模型函数
    theta:系数 matrix[mx1]  m为变量数
    x:自变量 matrix[nxm]  n为样本数   m为变量数 最后一列为1
    return: matrix[nx1]
    '''
    temp=(theta.T * x.T).T
    rows=temp.shape[0]
    yval=[]
    if rows==1:
        yval.append(_sign_fun(temp[0,0],0))
        return numpy.matrix(yval)
    elif rows>1:
        for index in range(rows):
            yval.append(_sign_fun(temp[index,0],0))
        
        return numpy.matrix(yval).T
            

def _sign_fun(x,b):
    '''
    sign函数  符号判别函数
    x:自变量 
    b:参考变量
    '''
    if x>=b:
        return 1
    else:
        return -1

def loss_fun(theta,x,y):
    '''
    损失函数计算
    统计误分类的函数距离
    theta:系数 matrix[mx1]
    x:自变量 matrix[nxm] 最后一列为1
    y:因变量 matrix[nx1]
    return:损失函数 误分类样本点的函数间隔之和取负号
    '''
    rows=y.shape[0]
    loss_sum=0
    for index in range(rows):
        temp=fun_distance(theta,x[index,:],y[index,:])
        if temp[0,0]<0:
            loss_sum=loss_sum+temp
    
    return -1*loss_sum
        
def grad_fun(x,y):
    '''
    计算损失函数的梯度
    随机梯度法 每次只是用某个误分类样本点的梯度
    x:自变量  matrix[1xm]
    y:因变量  matrix[1x1]
    return:梯度 matrix[mx1]
    '''
    return -1*(y*x).T
    
def fun_distance(theta,x,y):
    '''
     函数间隔计算       
     theta:系数 matrix[mx1]
    x:自变量 matrix[1xm] 第一列为1
    y:因变量 matrix[1x1]
    return: 函数间隔 matrix[1x1]
    '''
    return y*(theta.T * x.T)
    
def residul_plot(loss):
    '''
    绘制残差曲线
    '''
    rs_fig=plt.figure(figsize=(7,5))
    ax=rs_fig.add_axes([0.14,0.14,0.85,0.85])
    ax.plot(loss,'-r')
    plt.show()

def model_plot(theta):
    '''
    绘制模型函数的图像
    theta: matrix[mx1]
    ax+by+c=0
    '''
    a=theta[0,0]
    b=theta[1,0]
    c=theta[2,0]
    d=theta[3,0]
    
    meshNum=50j
    xMesh,yMesh=numpy.mgrid[0:1:meshNum,0:1:meshNum]
    zMesh=numpy.zeros((50,50))
    
    for i in range(50):     
        for j in range(50):
            zMesh[i,j]=(-a/c)*xMesh[i,j]+(-b/c)*yMesh[i,j]+(-d/c)
                
    ax_model.plot_surface(xMesh,yMesh,zMesh,rstride=1,cstride=1,cmap=plt.cm.hsv,linestyles='dashdot',linewidth=0.5,alpha=0.3)
    
    
 
def model_test(theta):
    '''
    进行模型测试
    theta:系数    最后一项对应自变量的0阶项
    '''
    pass
    
    
    
    
def PLA_main():
    '''
    PLA算法主函数
    '''
    rtheta=numpy.matrix([[1],[1],[1],[-1.2]])
    temp=create_data(rtheta)
    
    x_one=numpy.matrix(numpy.ones((num,1)))
    x=numpy.hstack((temp[:,0:3],x_one))
    y=temp[:,3]
    
    theta=numpy.matrix([[1,-1,-1,-1]]).T
    t=0.5
    index=0
    flag=0
    loss=[]
    while(True):
        
        if index==num:
            index=0
        if flag==num:
            break
        
        loss.append(loss_fun(theta,x,y))
        dist=fun_distance(theta,x[index,:],y[index,0])
        flag=flag+1
        if dist[0,0]<0:
            delta=grad_fun(x[index,:],y[index,0])
            theta=theta-t*delta
            flag=0
        
        index=index+1
    
    model_plot(theta)
    residul_plot(loss)
    model_test(theta)
     
if __name__=="__main__":
    PLA_main()
